/*
 * Copyright (C) Tildeslash Ltd. All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU Affero General Public License in all respects
 * for all of the code used other than OpenSSL.
 */

#include "config.h"

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include "monit.h"
#include "base64.h"


/**
 *  Implementation of base64 encoding/decoding.
 *
 *  @file
 */


/* ----------------------------------------------------------------- Private */


/**
 * Base64 encode one byte
 */
static char encode(unsigned char u) {
        if (u < 26)
                return 'A' + u;
        if (u < 52)
                return 'a' + (u - 26);
        if (u < 62)
                return '0' + (u - 52);
        if (u == 62)
                return '+';
        return '/';
}


/**
 * Decode a base64 character
 */
static unsigned char decode(char c) {
        if (c >= 'A' && c <= 'Z')
                return(c - 'A');
        if (c >= 'a' && c <= 'z')
                return(c - 'a' + 26);
        if (c >= '0' && c <= '9')
                return(c - '0' + 52);
        if (c == '+')
                return 62;
        return 63;
}


/**
 * Return true if 'c' is a valid base64 character, otherwise false
 */
static bool is_base64(char c) {
        return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || (c == '+') || (c == '/') || (c == '=')) ? true : false;
}


/* ------------------------------------------------------------------ Public */


/**
 * Base64 encode and return size data in 'src'. The caller must free the
 * returned string.
 * @param size The size of the data in src
 * @param src The data to be base64 encode
 * @return encoded string otherwise NULL
 */
char *encode_base64(size_t size, unsigned char *src) {
        if (! src)
                return NULL;
        if (! size)
                size = strlen((char *)src);
        char *out = CALLOC(sizeof(char), size * 4 / 3 + 4);
        char *p = out;
        for (size_t i = 0; i < size; i += 3) {
                unsigned char b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0, b7 = 0;
                b1 = src[i];
                if (i + 1 < size)
                        b2 = src[i + 1];
                if (i + 2 < size)
                        b3 = src[i + 2];
                b4 = b1 >> 2;
                b5 = ((b1 & 0x3) << 4) | (b2 >> 4);
                b6 = ((b2 & 0xf) << 2) | (b3 >> 6);
                b7 = b3 & 0x3f;
                *p++ = encode(b4);
                *p++ = encode(b5);
                if (i + 1 < size)
                        *p++ = encode(b6);
                else
                        *p++ = '=';
                if (i + 2 < size)
                        *p++ = encode(b7);
                else
                        *p++ = '=';
        }
        return out;
}


/**
 * Decode the base64 encoded string 'src' into the memory pointed to by
 * 'dest'. The dest buffer is <b>not</b> NUL terminated.
 * @param dest Pointer to memory for holding the decoded string.
 * Must be large enough to receive the decoded string.
 * @param src A base64 encoded string.
 * @return the length of the decoded string if decode
 * succeeded otherwise 0.
 */
size_t decode_base64(unsigned char *dest, const char *src) {
        if (src && *src) {
                unsigned char *p = dest;
                size_t k, l = strlen(src) + 1;
                unsigned char *buf = CALLOC(1, l);

                /* Ignore non base64 chars as per the POSIX standard */
                for (k = 0, l = 0; src[k]; k++)
                        if (is_base64(src[k]))
                                buf[l++] = src[k];
                for (k = 0; k < l; k += 4) {
                        char c1 = 'A', c2 = 'A', c3 = 'A', c4 = 'A';
                        unsigned char b1 = 0, b2 = 0, b3 = 0, b4 = 0;

                        c1 = buf[k];
                        if (k + 1 < l)
                                c2 = buf[k + 1];
                        if (k + 2 < l)
                                c3 = buf[k + 2];
                        if (k + 3 < l)
                                c4 = buf[k + 3];
                        b1 = decode(c1);
                        b2 = decode(c2);
                        b3 = decode(c3);
                        b4 = decode(c4);
                        *p++ = ((b1 << 2) | (b2 >> 4));
                        if (c3 != '=')
                                *p++ = (((b2&0xf) << 4) | (b3 >> 2));
                        if (c4 != '=')
                                *p++ = (((b3 & 0x3) << 6) | b4);
                }
                FREE(buf);
                return (p - dest);
        }
        return 0;
}

